{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 什么是链？\n",
    "> 链是连接LangChain组件、管理租组件数据流动的“包装器”。以确保整个LLM的工作流是一个有效的闭环：从提示词->语言链->检索器->输出解析器。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.llms.openai.OpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAI`.\n",
      "  warn_deprecated(\n"
     ]
    }
   ],
   "source": [
    "# LLM链的Demo\n",
    "from langchain.llms import OpenAI\n",
    "from langchain.prompts import PromptTemplate\n",
    "import os\n",
    "\n",
    "# 构建LLM\n",
    "llm = OpenAI(\n",
    "    temperature=0.9,\n",
    "    openai_api_key=os.environ['OPENAI_API_KEY']\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建提示词模板\n",
    "prompt = PromptTemplate(\n",
    "    input_variables = ['product'],\n",
    "    template = \"What is a good name for a company that makes {product}?\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建LLM链\n",
    "from langchain.chains import LLMChain\n",
    "chain = LLMChain(\n",
    "    llm=llm,\n",
    "    prompt=prompt,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `run` was deprecated in LangChain 0.1.0 and will be removed in 0.2.0. Use invoke instead.\n",
      "  warn_deprecated(\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'\\n\\nRainbow Feet Co.'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 运行LLM链\n",
    "chain.run(\"colorful socks\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 链的正确打开方式\n",
    "1. 准备输入：首先，需要准备一些输入，输入的数据类型应为一个有效的Python字典，其中键由提示词内的占位槽变量（即input_variables）构成，我们需要根据实际的prompt对象来确定需要哪些输入。\n",
    "2. 实例化链：从`langchain.chains`导入所需的LLM链，并传递一个有效的`PromptTemplate`和与之对应的llm基类。\n",
    "3. 运行链：使用以下函数来运行LLM链\n",
    "- `run()`：以同步状态运行LLM请求到服务器\n",
    "- `arun()`：以异步状态运行LLM请求到服务器\n",
    "- `apply()`：利用LLM生成方法来提高速度  \n",
    "\n",
    "\n",
    "这些方法都提供了一些可选参数：\n",
    "- inputs：字典类型，用于填充到提示词的变量字典\n",
    "- return_only_outputs(Optional)：布尔值，用于控制是否只返回输出。当为True时，则只返回由这个链生成的新键，反之为False时则返回输入键和由这个键生成的新键，默认为False。\n",
    "- callbacks(Optional)：布尔值，用于设置Chain运行时需要调用时的回调函数集合。\n",
    "- include_run_info(Optional)：表示是否在响应中包含运行信息。默认为False。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n",
      "  warn_deprecated(\n",
      "/home/elin/anaconda3/envs/langchain/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `__call__` was deprecated in LangChain 0.1.0 and will be removed in 0.2.0. Use invoke instead.\n",
      "  warn_deprecated(\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'adjective': 'corny',\n",
       " 'text': 'Why did the scarecrow win an award?\\n\\nBecause he was outstanding in his field!'}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## 当有多个输入时的演示\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "chat = ChatOpenAI(temperature=0,openai_api_key=os.environ['OPENAI_API_KEY'])\n",
    "\n",
    "prompt_template = \"Tell me a {adjective} joke\"\n",
    "\n",
    "llm_chain = LLMChain(\n",
    "    llm = chat,\n",
    "    prompt = PromptTemplate.from_template(prompt_template)\n",
    ")\n",
    "\n",
    "# 运行LLM链\n",
    "llm_chain(\n",
    "    inputs = {\"adjective\":\"corny\"}\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`run()`和`LLMChain.__call__()`并不相同，虽然它们的输入都是一个有效的字典，但是其返回的值永远是一个由输出解析器解析后的字符串。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用verbose参数来debugging 链\n",
    "from langchain.chains import ConversationChain\n",
    "from langchain.memory import ConversationBufferMemory\n",
    "\n",
    "conversation = ConversationChain(\n",
    "    llm = chat,\n",
    "    memory = ConversationBufferMemory(), # 使用基于BaseMemory()的记忆类来强化模型的记忆\n",
    "    verbose = True\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基础链类型\n",
    "- LLM链(LLMChain)：由提示词模板和模型包装器组成。用于将模板提示词传递给LLM并得到其回应。\n",
    "- 路由器链(RouterChain)：用于动态地选择给定输入的下一条链。路由器链由两部分组成：路由器链本身和目标链。\n",
    "- 顺序链(SequentialChain)：顺序链在调用LLM的下一步使用，它特别适合将一次调用的输出作为另一次调研费输入的场景。  \n",
    "\n",
    "其中分为：\n",
    "- `SimpleSequentialChain`，针对每一个链都有单一输入和输出的场景。\n",
    "- `SequentialChain`：针对每一个链具备多个输入和输出的场景。\n",
    "\n",
    "转换链(Transformation Chain)：用于数据交换，开发者定义自定义`transform()`函数来执行任何数据转换逻辑。该函数接受一个字典(其键由input_variables指定)作为参数并返回另一个字典(其键由output_variables指定)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sqlite3.Cursor at 0x7f87b498f7c0>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 下载预先准备的数据\n",
    "import sqlite3\n",
    "import requests\n",
    "\n",
    "# 下载SQL\n",
    "text = requests.get(\"https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql\").text\n",
    "\n",
    "\n",
    "# 创建数据库\n",
    "db = sqlite3.connect(\"./Chinook.db\")\n",
    "\n",
    "# 执行SQL\n",
    "db.executescript(text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n",
      "How many employees are there?\n",
      "SQLQuery:\u001b[32;1m\u001b[1;3mSELECT COUNT(*) FROM Employee\u001b[0m\n",
      "SQLResult: \u001b[33;1m\u001b[1;3m[(8,)]\u001b[0m\n",
      "Answer:\u001b[32;1m\u001b[1;3m8\u001b[0m\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'8'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载SQL工具链\n",
    "from langchain import OpenAI,SQLDatabase\n",
    "from langchain_experimental.sql import SQLDatabaseChain\n",
    "\n",
    "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")\n",
    "\n",
    "llm = OpenAI(temperature=0,verbose=True)\n",
    "\n",
    "db_chain = SQLDatabaseChain.from_llm(llm,db,verbose=True)\n",
    "\n",
    "db_chain.run(\"How many employees are there?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基础链-LLM链"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载原新闻\n",
    "import requests\n",
    "import bs4\n",
    "\n",
    "html = requests.get(\"https://techcrunch.com/2023/02/21/coinbase-shares-rise-q4-2022/\").text\n",
    "\n",
    "soup = bs4.BeautifulSoup(html)\n",
    "\n",
    "text = soup.get_text()\n",
    "\n",
    "outputText = \"\"\n",
    "\n",
    "ptagList = soup.find(\"div\", class_=\"article-content\").find_all(\"p\")\n",
    "\n",
    "for ptag in ptagList:\n",
    "    outputText += ptag.get_text()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.llms import OpenAI\n",
    "from langchain.chains import LLMChain\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "llm = OpenAI(temperature=0.9,openai_api_key=os.environ['OPENAI_API_KEY'])\n",
    "\n",
    "# 创建一个用于提取事实的提示词模板\n",
    "fact_extraction_prompt = PromptTemplate(\n",
    "    input_variables = [\"text_input\"],\n",
    "    template = (\n",
    "        \"Extract the key facts out of this text.Don't include opinions.\"\n",
    "        \"Give each fact a number and keep them short sentences.:\\n\\n\"\n",
    "        \"{text_input}\"\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "1. Coinbase released its Q4 2022 earnings report.\n",
      "2. Revenue in Q4 was $605 million, down from $2.49 billion in the same period last year.\n",
      "3. Trading volume and revenues decreased due to a 64% decline in overall crypto market capitalization.\n",
      "4. Coinbase's stock has risen 86% year-to-date.\n",
      "5. Trading revenue in Q4 was lower than in Q3.\n",
      "6. Subscription and services revenue increased by 34% in Q4.\n",
      "7. The number of monthly active developers in crypto has more than doubled since 2020.\n",
      "8. Major brands like Starbucks, Nike, and Adidas have entered the crypto space.\n",
      "9. Trading volume for both consumer and institutional users decreased in Q4.\n",
      "10. The crypto industry is hoping for greater adoption and trading volume.\n",
      "11. It is unclear if trading interest will pick back up in 2023 or if Coinbase will rely on other sources of revenue.\n"
     ]
    }
   ],
   "source": [
    "# 创建LLM链\n",
    "fact_extraction_chain = LLMChain(llm=llm,prompt=fact_extraction_prompt)\n",
    "\n",
    "facts = fact_extraction_chain.run(outputText)\n",
    "\n",
    "print(facts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 顺序链"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义模板\n",
    "investor_update_prompt = PromptTemplate(\n",
    "    input_variables = [\"facts\"],\n",
    "    template = \"You are a Goldman Sachs analyst.Take the following list of facts and use them to write a short paragrah for investors.Don't leave out key info:\\n\\n {facts}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "According to Coinbase's Q4 2022 earnings report, the company's revenue in the fourth quarter was $605 million, showing a decline from $2.49 billion in the same period last year. This decrease is largely attributed to the overall 64% decline in crypto market capitalization. Despite this, Coinbase's stock has seen an 86% increase year-to-date, indicating a strong performance compared to other companies in the crypto space. It is worth noting that trading revenue in Q4 was lower than in Q3, however, subscription and services revenue saw a healthy increase of 34%. Additionally, there has been a significant increase in the number of monthly active developers in the crypto industry, with major brands like Starbucks, Nike, and Adidas now involved in the space. While Q4 saw a decrease in trading volume for both consumer and institutional users, the industry is hopeful for greater adoption and trading volume in the future. As investors, it is important to note that it is unclear if trading interest will pick back up in 2023 or if Coinbase will rely on other sources of revenue. \n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1087"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 以常规方式运行链，以撰写摘要\n",
    "investor_update_chain = LLMChain(llm = llm,prompt = investor_update_prompt)\n",
    "investor_update = investor_update_chain.run(facts)\n",
    "\n",
    "print(investor_update)\n",
    "len(investor_update)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n",
      "\u001b[36;1m\u001b[1;3m\n",
      "\n",
      "1. Coinbase released its Q4 2022 earnings report on Tuesday.\n",
      "2. The company's shares are down in early after-hours trading.\n",
      "3. In Q4 2022, Coinbase generated $605 million in total revenue, down from $2.49 billion in the previous year.\n",
      "4. The company reported a net loss of $557 million on a GAAP basis and an adjusted EBITDA deficit of $124 million in Q4.\n",
      "5. Wall Street expected revenue of $581.2 million and adjusted EBITDA of -$201.8 million.\n",
      "6. Coinbase's stock had risen 86% year-to-date.\n",
      "7. The value of Coinbase, measured on a per-share basis, is down significantly from its 52-week high.\n",
      "8. Consumer and institutional trading volumes declined in Q4 2022.\n",
      "9. The overall crypto market capitalization fell by $1.5 trillion in 2022.\n",
      "10. Coinbase's total trading volumes and revenues fell 50% and 66% year-over-year, respectively.\n",
      "11. Trading revenue at Coinbase also declined in Q4 compared to the previous quarter.\n",
      "12. Coinbase saw growth in its \"subscription and services revenue\" in Q4.\n",
      "13. The number of monthly active developers in the\u001b[0m\n",
      "\u001b[33;1m\u001b[1;3m Coinbase ecosystem increased by 44% in Q4.\n",
      "\n",
      "\n",
      "Investors should take note of Coinbase's Q4 2022 earnings report, which was released on Tuesday. While the company's shares are down in early after-hours trading, there are some key factors to consider. Despite a decline in total revenue to $605 million, down from $2.49 billion in the previous year, Coinbase reported growth in its \"subscription and services revenue\" and an increase of 44% in monthly active developers within their ecosystem. However, the company did report a net loss of $557 million on a GAAP basis and an adjusted EBITDA deficit of $124 million. This is below the Wall Street expectations of $581.2 million in revenue and an adjusted EBITDA of -$201.8 million. It is worth noting that Coinbase's stock had risen 86% year-to-date but is currently down significantly from its 52-week high. This decline can be attributed to a 50% decrease in total trading volumes and a 66% decrease in revenues, as well as a decline in both consumer and institutional trading volumes. This can be understood in the context of the overall crypto market, which saw a $1.5 trillion decline in market capitalization in 202\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "# 使用简单组合链将事实提取和摘要进行结合\n",
    "from langchain.chains import SimpleSequentialChain,SequentialChain\n",
    "\n",
    "full_chain = SimpleSequentialChain(\n",
    "    chains = [fact_extraction_chain,investor_update_chain],\n",
    "    verbose = True\n",
    ")\n",
    "\n",
    "response = full_chain.run(outputText)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 四大文档合并链\n",
    "- Stuff链：接受一组文档，将它们全部插入一个提示中，然后将其传递给LLM。这种链适合用于文档较小且大部分调用只传入少量文档的应用程序。\n",
    "- Refine链：通过便利输入文档并爹地啊更新其答案来构建响应。对于每个文档，它将所有非文档输入、当前文档和最新的中间答案传递给LLM链，以获得新的答案。由于Refine链一次只向LLM传递一个文档，因此它非常适合需要分析模型上下文容纳不下的文档任务。很显然，这种链会比Stuff链调用更多的LLM链。此外，还有一些任务很难通过迭代来完成。例如，当文档经常相互交叉引用或任务需要许多文档的详细之间，Refine链的表现可能较差。\n",
    "- MapReduce链：首先将LLM链单独应用于每个文档(Map)，并将链输出视为新的文档。然后，它将所有新文档传递给一个单独的文档链，以获得单一的输出(Reduce)。如有需要，这个压缩步骤将递归进行。\n",
    "- 重排链(MapRerank)：与MapReduce链一样，对每个文档运行一个初始提示的指令微调。这个初始提示不仅试图完成一个特定任务（比如回答一个问题或执行一个动作），也为其答案提供了一个置信度得分。然后，这个得分被用来重新排序所有的文档或条目。最终，得分最高的响应被返回。这种机制有助于在多个可能得答案或解决方案，找到最适合、最准确或最相关的一个。重排链通过添加一个重排序或重打分步骤，进一步提高系统性能和准确性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### LEDVR工作流的终点：“上链”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 首先从网络加载文档\n",
    "import requests\n",
    "import bs4\n",
    "from langchain_core.documents.base import Document\n",
    "\n",
    "html = \"https://www.runoob.com/rust/rust-tutorial.html\"\n",
    "\n",
    "html = requests.get(html).text\n",
    "\n",
    "soup = bs4.BeautifulSoup(html)\n",
    "\n",
    "text = soup.get_text()\n",
    "\n",
    "ptagList = soup.find(\"div\", class_=\"article-body\").find_all(\"p\")\n",
    "\n",
    "cleaned = \"\"\n",
    "\n",
    "for ptag in ptagList:\n",
    "    cleaned += ptag.get_text()\n",
    "\n",
    "cleaned = cleaned.replace(\"\\n\",\"\").replace(\"\\r\",\"\")\n",
    "\n",
    "# 创建文档\n",
    "\n",
    "doc = Document(\n",
    "    page_content=cleaned,\n",
    "    language = \"No language found.\"\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 记载OpenAI嵌入\n",
    "from langchain.embeddings.openai import OpenAIEmbeddings\n",
    "embedding = OpenAIEmbeddings(openai_api_key=os.environ['OPENAI_API_KEY'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建字符分割\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=0)\n",
    "\n",
    "splits = text_splitter.split_documents([doc])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 初始化FAISS向量数据库\n",
    "from langchain.vectorstores import FAISS\n",
    "vectordb = FAISS.from_documents(documents=splits,embedding=embedding)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 初始化检索器\n",
    "retriever = vectordb.as_retriever()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 初始化LLM\n",
    "from langchain.llms import OpenAI\n",
    "# 导入对话检索链\n",
    "from langchain.chains import ConversationalRetrievalChain\n",
    "llm = OpenAI(openai_api_key = os.environ['OPENAI_API_KEY'])\n",
    "\n",
    "qa = ConversationalRetrievalChain.from_llm(llm=llm,retriever=retriever)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "' Rust是由Mozilla开发的。'"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "query = \"Rust是由谁编写的？\"\n",
    "result = qa({\"question\":query,\"chat_history\":\"\"})\n",
    "\n",
    "result['answer']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
